package com.mfz.common.servlet

import com.alibaba.fastjson.JSONArray
import com.alibaba.fastjson.JSONObject
import com.mfz.common.dbHelper.JDBCHelperFactory
import com.mfz.common.dbHelper.containsThreadDBHelper
import com.mfz.common.definition.organization.User
import com.mfz.common.definition.symbol.Flag
import com.mfz.common.definition.symbol.Type
import com.mfz.common.encryption.Encryption
import com.mfz.common.encryption.EncryptionMode
import com.mfz.common.encryption.NotEncryption
import com.mfz.common.encryption.rsa.RsaServer
import com.mfz.common.encryption.simple.SimpleEncryptPublic
import com.mfz.common.servlet.HttpRequestType.*
import com.mfz.common.units.FLAG
import com.mfz.common.units.NAME
import com.mfz.common.units.TYPE
import org.apache.logging.log4j.LogManager
import java.math.BigDecimal
import java.text.SimpleDateFormat
import java.util.*
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

private const val METHOD_POST = "POST"
private const val METHOD_OPTIONS = "OPTIONS"
private const val HEADER_ENCRYPTION = "Encryption"
private const val HEADER_TOKEN = "Token"
private const val HEADER_USERAGENT = "User-Agent"
/**
 * 当前登录者是使用APP方式登录
 */
private const val APP_USERAGENT = "SelfApplication"
private const val OFF = "off"
private const val CONTENT_TYPE = "Content-Type"
private const val CONTENT_TYPE_JSON = "application/json"
private const val RESULT_CODE = "code"

const val CONDITION = "condition"
const val WHERE = "where"
const val ORDER_BY = "order_by"
private const val CONDITION_NAME = NAME
const val DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"
const val SORTING = "sorting"

/**
 *
 * 返回：
 * 状态码：
 *    此方法直接返回http的状态码。在http本身的状态码上进行扩展。
 *    成功返回状态码200.
 *    304将不会被此业务逻辑返回
 * TOKEN：返回时将在头档指明一个token标示。WEB端与微信端均需要在下次请求时将此token带回。
 *  未带回一定返回未登录操作。
 *
 * 标明用户：
 *  WEB端：由于WEB端无任何的信任状态。已经登录会返回一个对应的token头档值.
 *     下次请求时将token原值传回
 *     如果标明已超时，或未登录。则需要再次进行登录操作
 *  APP端：需要指定USER-agent:SelfApplication。
 *    app将保留最后一次通信所获得的token 与用户侧密钥。
 *    并在下次请求时将此token原值传回
 *    并且将body内的值进行加密操作。
 *    收到的消息时，查看头档  encrypt 标明的加密方式：
 *    如果此值为simple，则为简单加密操作。
 *    此值为rsa，则为使用RSA加密方式。
 *    为off或无此头档，则为非加密
 *  微信端：同WEB端，如标明已超时，则需要再次进行登录操作。
 *
 * 加密：当有加密情况，头档指示 encrypt：on.
 *    如无加密,头档指示encryption：off 或者无此标识
 * create by 2019-06-19.
 * @author yimin
 */
abstract class BaseServlet : HttpServlet() {
	companion object {
		val log = LogManager.getLogger(BaseServlet::class.java.name)!!
	}

	/**
	 * 指明使用此servlet，必须指定用户。
	 */
	protected abstract val mustExistsOperator: Boolean
	/**
	 * 用户权限的基本值。由继承类指定。如果此值指定为<0的值，表示此接口无需任何权限控制
	 */
	protected abstract val basePermission: Int

	public override fun service(req: HttpServletRequest, res: HttpServletResponse) {
		if (!setSurroundings(req, res)) return

		//在这里会指定数据库处理方式为使用事务的处理方式
		try {
			JDBCHelperFactory.default.getThreadHelper()
			log.trace("请求类型:${req.getHeader(CONTENT_TYPE)}")
			val result = when (req.getHeader(CONTENT_TYPE).toLowerCase().split(';')[0]) {
				//业务处理的JSON格式的对象
				CONTENT_TYPE_JSON -> handleJSON(req, res)
				//后续如果需要处理相应的二进制请求时，应该走其他的流程。
				else -> {
					log.warn("未处理的请求类型:${req.getHeader(CONTENT_TYPE)}")
					returnError(res, ERROR_REQUEST.message, ERROR_REQUEST.code)
				}
			}

			//如果是成功，提交代码。如果code是失败则回滚
			releaseDBHelper(result.code in 200..299)
		} catch (e: Exception) {
			log.error("发生了异常：$e")
			releaseDBHelper(false)

			returnError(res, UNKNOWN.message, UNKNOWN.code)
		}
	}

	/**
	 * 这个动作关闭获得的对应连接线程对象的数据库连接。
	 * 就算某一个servlet未获得，也将进行获得再关闭。好象多此一举
	 */
	private fun releaseDBHelper(commit: Boolean) {
		if (containsThreadDBHelper()) {
			val helper = JDBCHelperFactory.default.getThreadHelper()

			helper.close(true, closeThread = true, commit = commit)
		}
	}

	/**
	 * 这是一个接收到json消息的流程。
	 *
	 */
	private fun handleJSON(req: HttpServletRequest, res: HttpServletResponse): HttpResult {
		val parameter = organizeParameter(req, res)
		if (parameter.first != null) {
			returnError(res, parameter.first!!.message, parameter.first!!.code)
			return UNKNOWN
		}

		val context = parameter.second!!

		//进行权限的判断,没有权限的时候返回
		if (mustExistsOperator && !haveOperatorPower(context.user!!, context.requestType)) {
			returnError(res, NO_OPERATOR_PERMISSION.message, NO_OPERATOR_PERMISSION.code)
			return NO_OPERATOR_PERMISSION
		}

		try {
			//调用检测上传参数是否正确
			context.result = checkParameters(context.requestData, context.requestType, context)
			if (context.result !is Success) {
				writeReturnMessage(context, context.result)
				return context.result
			}
			//调用子类进行处理的方法
			context.result = processingBusiness(context)
			//写返回值
			writeReturnMessage(context, context.result)

			//进行操作日志的记录
			recordOperatorLog(context)
		} catch (e: Exception) {
			log.error("发生未捕获异常：", e)
			context.result = UNKNOWN
			writeReturnMessage(context, UNKNOWN)
		} finally {
			context.close()
		}

		return context.result
	}

	/**
	 * 判断当前用户是否具有操作的权限
	 */
	private fun haveOperatorPower(user: User, reqType: HttpRequestType): Boolean {
		return if (basePermission < 0 || reqType.value <= 0)
			true
		else
			user.hasPermission(basePermission * 100 + reqType.value)
	}

	/**
	 * 整理上传来的参数。进行相关的判断。
	 *
	 * 返回一个Pair 对象。如果此方法成功，HttpResult 为 null,HttpContext 即为参数值。
	 * 如果此方法失败，返回 HttpContext 为 null,HttpResult 为错误码。
	 *
	 * 返回两个空值，为已经给定了返回的结果。不再需要再次进行返回设置
	 */
	private fun organizeParameter(req: HttpServletRequest, res: HttpServletResponse): Pair<HttpResult?, HttpContext?> {
		val typeString = req.getHeader(TYPE) ?: return Pair(UNKNOWN_TYPE, null)

		log.info("${javaClass.simpleName}类收到的请求类型为：$typeString")

		//得到操作类型
		val type = try {
			valueOf(typeString.toUpperCase())
		} catch (e: Exception) {
			log.error("出现一个错误：", e)
			return Pair(UNKNOWN_TYPE, null)
		}


		//得到用户
		val userResult = getUser(req)
		if (mustExistsOperator) {
			if (userResult.first != null) return Pair(userResult.first, null)
		}

		val user = userResult.second
//		log.debug("当前用户：$user")

		//得到加密的方式的前端指定。并且处理各种异常
		val encryption = getEncryption(req, user)
		if (encryption.first != null) return Pair(encryption.first, null)
		if (encryption.second == null) return Pair(UNKNOWN, null)

		val text = getBodyByJson(user, encryption.second!!, req, res) ?: return Pair(ERROR_PARAMETER, null)
		log.info("得到的消息：$text")

		val data = try {
			JSONObject.parseObject(text)
		} catch (e: Exception) {
			return Pair(ERROR_PARAMETER, null)
		}

		return Pair(null, HttpContext(type, data, user, encryption.second!!, req, res))
	}

	private fun getBodyByJson(
		user: User?,
		encryption: Encryption,
		req: HttpServletRequest,
		res: HttpServletResponse
	): String? {
		if (user == null && encryption !is NotEncryption) {
			returnError(res, "未指定用户时，不能使用加密方法")
			return null
		}

		val body = req.reader.readText()
		return try {
			decrypt(encryption, body)
		} catch (e: Exception) {
			returnError(res, ERROR_DECRYPT.message, ERROR_DECRYPT.code)
			null
		}
	}

	/**
	 * 构建加密算法对象。
	 * 根据不同用户类型,得到的加密算法类型也不一样
	 */
	private fun getEncryption(req: HttpServletRequest, user: User?): Pair<HttpResult?, Encryption?> {
		val encryptionMode = try {
			EncryptionMode.valueOf((req.getHeader(HEADER_ENCRYPTION) ?: OFF).toUpperCase())
		} catch (e: Exception) {
			log.error("处理加密类型时出现异常。", e)
			return Pair(INVALID_ENCRYPTION, null)
		}

		val encryption = try {
			when (encryptionMode) {
				EncryptionMode.RSA -> {
					if (user == null) return Pair(ENCRYPTION_OBJECT_ERROR, null)

					RsaServer(Base64.getDecoder().decode(user.encryptionKey))
				}
				EncryptionMode.SIMPLE -> {
					if (user == null) return Pair(ENCRYPTION_OBJECT_ERROR, null)

					SimpleEncryptPublic(user.encryptionKey)
				}
				EncryptionMode.OFF -> NotEncryption()
//			else -> return Pair(INVALID_ENCRYPTION, null)
			}
		} catch (e: Exception) {
			log.error("加密对象出现异常。", e)
			return Pair(ENCRYPTION_OBJECT_ERROR, null)
		}

		return Pair(null, encryption)
	}

	/**
	 * 返回操作处理前的异常操作
	 */
	private fun returnError(res: HttpServletResponse, message: String, code: Int = ERROR_REQUEST.code): HttpResult {
//		res.setHeader(CONTENT_TYPE, "Text")
		res.setHeader(RESULT_CODE, code.toString())
		res.status = 200
		res.writer.print("{\"message\":\"$message\"}")
		return UNKNOWN
	}

	/**
	 * 得到用户
	 * 目前是根据token得到用户的相关信息
	 */
	private fun getUser(req: HttpServletRequest): Pair<HttpResult?, User?> {
		val token = req.getHeader(HEADER_TOKEN) ?: return Pair(INVALID_TOKEN, null)
		if (token.isEmpty() || token == "null") return Pair(INVALID_TOKEN, null)

		val httpToken = try {
			HttpToken(token)
		} catch (e: Exception) {
			log.error("上传了一个非法的token,值:$token..异常：", e)
			return Pair(INVALID_TOKEN, null)
		}

		//app不进行请求是否超时，需要重新登录的判断
		if (req.getHeader(HEADER_USERAGENT) != APP_USERAGENT)
			if (!httpToken.checkTokenEffective())
				return Pair(USER_NOT_LOGIN, null)

		//判断得到的token是否有效，无效返回相应的无效信息
		return if (!httpToken.isValid)
			Pair(USER_NOT_LOGIN, null)
		else Pair(null, httpToken.user)
	}

	/**
	 * 初始化环境操作。
	 *
	 * 返回是否功能。
	 */
	private fun setSurroundings(req: HttpServletRequest, res: HttpServletResponse): Boolean {
		req.characterEncoding = "UTF-8"
		res.characterEncoding = "UTF-8"
		res.setHeader(CONTENT_TYPE, "application/json;charset=UTF-8")
		res.setHeader("Access-Control-Allow-Credentials", "true")
		//允许任何站点访问，不能使用*
		res.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"))

		//处理相关的数据，
		return when {
			req.method == METHOD_OPTIONS -> {
				res.setHeader(
					"Access-Control-Allow-Headers",
					"Origin, X-Requested-With, Content-Type, Accept, Connection, User-Servicer, Cookie"
				)
				res.setHeader("Access-Control-Allow-Methods", req.getHeader("Access-Control-Request-Method"))
				false
			}
			req.method != METHOD_POST -> {
				returnError(res, "错误的请求方法")
				false
			}

			else -> true
		}
	}

	private fun writeReturnMessage(context: HttpContext, result: HttpResult) {
		val data =
			//如果上传来的对象是有加密的，将返回值也加密，并且设置头部加密标志。
			if (context.encryption !is NotEncryption) {
				context.response.setHeader(HEADER_ENCRYPTION, context.encryption.getMode())
				log.debug("向前台返回：$result")
				encrypt(context, result.data)
			} else {
				context.response.setHeader(HEADER_ENCRYPTION, OFF)
				log.debug("向前台返回：$result")
				result.data
			}

		val token = HttpToken(context.user).createToken()
		if (token != null)
			context.response.setHeader(HEADER_TOKEN, token)

		context.response.status = 200
		context.response.setHeader(RESULT_CODE, result.code.toString())
		context.response.writer.print(data)
	}

	//进行数据的加密操作
	private fun encrypt(context: HttpContext, data: String): String {
		return context.encryption.encrypt(data)
	}

	//进行数据的解密操作
	private fun decrypt(encryption: Encryption, data: String): String {
		return encryption.decrypt(data)
	}

	/**
	 * 由继承类进行编写和处理相关业务逻辑。
	 *
	 * context：上下文内容。
	 */
	abstract fun processingBusiness(context: HttpContext): HttpResult


	/**
	 * 根据给出的东西生成条件
	 *
	 * @param jsonData 包含上传的参数的json,不是servletData
	 */
	protected fun generateCondition(
		jsonData: JSONObject,
		special: String? = null,
		block: ((data: JSONObject) -> String)? = null
	): String {
		val dbHelp = JDBCHelperFactory.default
		return if (jsonData.containsKey(CONDITION)) {
			if ((jsonData[CONDITION] as JSONArray).size > 0) {
				buildString {
					for (data in (jsonData[CONDITION] as JSONArray)) {
						append(" and ")
						if (data is JSONObject) {
							if (block != null && data[CONDITION_NAME] == special)
								append(block(data))
							else
								when (data["operator"] as String) {
									"like" -> append("cast (t.").append(data[CONDITION_NAME]).append(" as varchar) like ").append(
										dbHelp.format("%${data["value"].toString().trim()}%")
									)

									"between" -> append("t.").append(data[CONDITION_NAME]).append(" between ").append(
										dbHelp.format(
											data["minvalue"]
										)
									).append(" and ").append(dbHelp.format(data["maxvalue"]))

									"in" -> {
										append("t.").append(data[CONDITION_NAME]).append(" in ").append(
											data["value"].toString().replace("[", "(").replace(
												"]",
												")"
											).replace("\"", "\'")
										)
									}

									"or" -> {
										append(" ( ").append("t.").append(data["minvalue"]).append(" = ")
											.append(data[CONDITION_NAME]).append(" or ").append("t.").append(data["maxvalue"])
											.append(" = ").append(dbHelp.format(data[CONDITION_NAME])).append(" ) ")
									}

									else -> append("t.").append(data[CONDITION_NAME]).append(data["operator"]).append(
										dbHelp.format(
											data["value"]
										)
									)
								}
						}
					}
				}
			} else ""
		} else ""
	}

	/**
	 * 根据条件生成 order by + 后续的语句  默认desc排序
	 *
	 * 传值需要传 sorting 数组   例如：["id desc","name asc"]
	 */
	protected fun sortDeskCondition(jsonData: JSONObject): String {
		return if (jsonData[SORTING] != null) {
			val jsonArray = jsonData[SORTING] as JSONArray
			return if (jsonArray.isNotEmpty()) {
				buildString {
					append(" order by ")
					jsonArray.forEach {
						append(" t.").append(it).append(" , ")
					}
					delete(length - 2, length)
				}
			} else " order by 1 desc "
		} else " order by 1 desc "
	}

	/**
	 * 检查参数的方法.
	 *
	 *
	 * 此方法在验证用户存在后被调用。
	 * 返回null为正常，返回对象将被直接返回前台操作处。
	 *
	 * @param data    前台传入的json格式的数据。
	 * @param type        操作类型
	 * @param context 打包后的此servlet所有数据
	 * @return 返回null正常。返回对象将被直接返回前台操作处。
	 */
	protected open fun checkParameters(data: JSONObject, type: HttpRequestType, context: HttpContext): HttpResult {
		return Success.it
	}

	private fun writeCorrectParaResult(result: HttpResult, message: String): HttpResult {
		val thisResult = if (result is Success) NoSetRequestType() else result

		thisResult.addToBuilder(message)
		return thisResult
	}

	/**
	 * 测试参数是否正确
	 */
	protected fun correctPara(name: String, data: JSONObject, type: ParaType, result: HttpResult? = null, minValue: Any? = null, maxValue: Any? = null, isNeed: Boolean = true): HttpResult {
		val thisResult = result ?: Success.it

		if (isNeed) {
			if (!data.containsKey(name)) {
				return writeCorrectParaResult(thisResult, "\"$name\":\"not_found\"")
			}
		} else if (!data.containsKey(name)) {
			return thisResult
		} else if (data[name] == null) {
			data.remove(name)
			return thisResult
		}

		val value = data[name] ?: return writeCorrectParaResult(thisResult, "\"$name\":{\"value\":null}")

		val returnValue = when (type) {
			ParaType.LONG -> {
				val v = when (value) {
					is String -> {
						if (value.isEmpty()) {
							return writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"is Empty\"}")
						} else
							value.toLong()
					}
					is Int -> value.toLong()
					else -> value
				}

				data[name] = v
				if ((v is Long) && (v <= (maxValue as Long?) ?: Long.MAX_VALUE) && (v >= (minValue as Long?) ?: Long.MIN_VALUE)) {
					thisResult
				} else {
					writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$v\",\"type\":\"${v::class.java}\",\"need_type\":\"${type.name}\",\"min_value\":$minValue,\"max_value\":$maxValue}")
				}
			}
			ParaType.INT -> {
				val v = when (value) {
					is String -> {
						if (value.isEmpty())
							return writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\",not Empty!")

						//处理特殊字段请求值，将名称换成对应的Int值
						when (name) {
							FLAG -> {
								Flag.findByName(value).id
							}
							TYPE -> {
								Type.findByName(value).id
							}
							else -> value.toInt()
						}

					}
					is Long -> value.toInt()
					else -> value
				}

				data[name] = v
				if ((v is Int) && (v <= (maxValue as Int?) ?: Int.MAX_VALUE) && (v >= (minValue as Int?) ?: Int.MIN_VALUE)) {
					thisResult
				} else {
					writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$v\",\"type\":\"${v::class.java}\",\"need_type\":\"${type.name}\",\"min_value\":$minValue,\"max_value\":$maxValue}")
				}
			}
			ParaType.STRING -> {
				val v = value.toString()

				if ((v.length <= (maxValue as Int?) ?: Int.MAX_VALUE) && (v.length >= (minValue as Int?) ?: Int.MIN_VALUE)) {
					data[name] = v.trim()
					thisResult
				} else {
					writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\",\"type\":\"${value::class.java.simpleName}\",\"need_type\":\"${type.name}\",\"min_value\":$minValue,\"max_value\":$maxValue}")
				}
			}

			ParaType.DATETIME -> if ((value is String) && value.length == 19) {
				val format = SimpleDateFormat(DATE_FORMAT)
				val date =
					try {
						format.parse(value)
					} catch (e: Exception) {
						return writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\",\"message\":\"无法转换成日期格式.\"}")
					}
				val minDate = if (minValue == null) 0L else (minValue as Date).time
				val maxDate = if (maxValue == null) Long.MAX_VALUE else (maxValue as Date).time
				if (date.time > maxDate || date.time < minDate) {
					writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\",\"type\":\"${value::class.java.simpleName}\",\"need_type\":\"${type.name}\",\"min_value\":${format.format(minValue)},\"max_value\":${format.format(maxValue)}}")
				} else
					thisResult
			} else {
				writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\",\"format\":\"$DATE_FORMAT\",\"need_type\":\"String\"}")
			}


			ParaType.DOUBLE -> {
				val v: Double = when (value) {
					is Int -> value.toDouble()
					is Long -> value.toDouble()
					is BigDecimal -> value.toDouble()
					is String -> try {
						value.toDouble()
					} catch (e: Exception) {
						Double.MIN_VALUE
					}
					is Double -> value
					else -> Double.MIN_VALUE
				}
				data[name] = v


				if (v != Double.MIN_VALUE && (v <= (maxValue as Double?) ?: Double.MAX_VALUE) && (v >= (minValue as Double?) ?: -1.0)) {
					thisResult
				} else {
					writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\",\"type\":\"${value::class.java}\",\"need_type\":\"${type.name}\",\"min_value\":$minValue,\"max_value\":$maxValue}")
				}
			}
			ParaType.BOOL -> if (value is Boolean) {
				thisResult
			} else {
				writeCorrectParaResult(thisResult, "\"$name\":{\"value\":$value,\"type\":\"${value::class.java}\",\"need_type\":\"${type.name}\"}")
			}
			ParaType.JSON -> if (value is JSONObject) {
				thisResult
			} else {
				writeCorrectParaResult(thisResult, "\"$name\":{\"value\":$value,\"type\":\"${value::class.java}\",\"need_type\":\"${type.name}\"}")
			}
			ParaType.ARRAY -> if (value is JSONArray) {
				value.forEach {
					if (it != null && (it.toString().contains("\t") || it.toString().contains("\n"))) {
						writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\"}")
					}
				}

				thisResult
			} else {
				writeCorrectParaResult(thisResult, "\"$name\":{\"value\":\"$value\",\"type\":\"${value::class.java}\",\"need_type\":\"${type.name}\"}")
			}
		}

		if (!isNeed) {
			when (type) {
				ParaType.LONG -> if (data[name] == -1L) data.remove(name)
				ParaType.INT -> if (data[name] == -1) data.remove(name)
				ParaType.STRING -> if (data[name] == "") data.remove(name)
				ParaType.DOUBLE -> if (data[name] == -1.0) data.remove(name)
				ParaType.JSON -> if ((data[name] as JSONObject).isEmpty()) data.remove(name)
				ParaType.ARRAY -> if ((data[name] as JSONArray).isEmpty()) data.remove(name)
				else -> {
				}
			}
		}

		return returnValue
	}

}


enum class ParaType {
	LONG, INT, STRING, DOUBLE, ARRAY, JSON, BOOL, DATETIME
}


/**
 * 进行实际的日志操作,当前这个操作为写LOG文件
 * 此操作方法可以被更改
 */
var recordOperatorLog = fun(context: HttpContext) {
	if (context.requestType in arrayOf(SELECT, TEST, LIST))
		return

	val format = SimpleDateFormat(DATE_FORMAT)
	BaseServlet.log.info("用户:${context.user?.get<String>(NAME) ?: "匿名用户"}在${format.format(Date())}进行了${context.requestType.description}操作,提交的内容为:${context.requestData},返回的结果为:${context.result}")
}
